#region Copyright (c) 2006-2010 Widgetsphere LLC, All Rights Reserved
//--------------------------------------------------------------------- *
//                          Widgetsphere  LLC                           *
//             Copyright (c) 2006-2010 All Rights reserved              *
//                                                                      *
//                                                                      *
//This file and its contents are protected by United States and         *
//International copyright laws.  Unauthorized reproduction and/or       *
//distribution of all or any portion of the code contained herein       *
//is strictly prohibited and will result in severe civil and criminal   *
//penalties.  Any violations of this copyright will be prosecuted       *
//to the fullest extent possible under law.                             *
//                                                                      *
//THE SOURCE CODE CONTAINED HEREIN AND IN RELATED FILES IS PROVIDED     *
//TO THE REGISTERED DEVELOPER FOR THE PURPOSES OF EDUCATION AND         *
//TROUBLESHOOTING. UNDER NO CIRCUMSTANCES MAY ANY PORTION OF THE SOURCE *
//CODE BE DISTRIBUTED, DISCLOSED OR OTHERWISE MADE AVAILABLE TO ANY     *
//THIRD PARTY WITHOUT THE EXPRESS WRITTEN CONSENT OF WIDGETSPHERE LLC   *
//                                                                      *
//UNDER NO CIRCUMSTANCES MAY THE SOURCE CODE BE USED IN WHOLE OR IN     *
//PART, AS THE BASIS FOR CREATING A PRODUCT THAT PROVIDES THE SAME, OR  *
//SUBSTANTIALLY THE SAME, FUNCTIONALITY AS ANY WIDGETSPHERE PRODUCT.    *
//                                                                      *
//THE REGISTERED DEVELOPER ACKNOWLEDGES THAT THIS SOURCE CODE           *
//CONTAINS VALUABLE AND PROPRIETARY TRADE SECRETS OF WIDGETSPHERE,      *
//INC.  THE REGISTERED DEVELOPER AGREES TO EXPEND EVERY EFFORT TO       *
//INSURE ITS CONFIDENTIALITY.                                           *
//                                                                      *
//THE END USER LICENSE AGREEMENT (EULA) ACCOMPANYING THE PRODUCT        *
//PERMITS THE REGISTERED DEVELOPER TO REDISTRIBUTE THE PRODUCT IN       *
//EXECUTABLE FORM ONLY IN SUPPORT OF APPLICATIONS WRITTEN USING         *
//THE PRODUCT.  IT DOES NOT PROVIDE ANY RIGHTS REGARDING THE            *
//SOURCE CODE CONTAINED HEREIN.                                         *
//                                                                      *
//THIS COPYRIGHT NOTICE MAY NOT BE REMOVED FROM THIS FILE.              *
//--------------------------------------------------------------------- *
#endregion
using System;
using System.Linq;
using Widgetsphere.Generator.Common.GeneratorFramework;
using Widgetsphere.Generator.Models;
using System.Text;
using Widgetsphere.Generator.Common.Util;
using System.Collections.Generic;

namespace Widgetsphere.Generator.EFDAL.Generators.EFMSL
{
	public class MSLTemplate : EFDALBaseTemplate
	{
		private StringBuilder sb = new StringBuilder();

		public MSLTemplate(ModelRoot model)
		{
			_model = model;
		}

		#region BaseClassTemplate overrides
		public override string FileName
		{
			get { return string.Format("{0}.msl", _model.ProjectName); }
		}


		public override string FileContent
		{
			get
			{
				try
				{
					GenerateContent();
					return sb.ToString();
				}
				catch (Exception ex)
				{
					throw;
				}

			}
		}

		#endregion

		#region GenerateContent

		private void GenerateContent()
		{
			sb.AppendLine("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
			sb.AppendLine("<Mapping Space=\"C-S\" xmlns=\"urn:schemas-microsoft-com:windows:storage:mapping:CS\">");
			sb.AppendFormat("	<EntityContainerMapping StorageEntityContainer=\"{0}ModelStoreContainer\" CdmEntityContainer=\"{0}Entities\">", _model.ProjectName).AppendLine();
			AppendTableEntityMappingSet();
			AppendRelationAssociationSetMapping();
			sb.AppendLine("	</EntityContainerMapping>");
			sb.AppendLine("</Mapping>");
		}

		private void AppendRelationAssociationSetMapping()
		{
			foreach (Relation relation in _model.Database.Relations)
			{
				Table parentEntity = (Table)relation.ParentTableRef.Object;
				Table childEntity = (Table)relation.ChildTableRef.Object;
				if (parentEntity != null && childEntity != null && parentEntity.Generated && childEntity.Generated && relation.IsGenerated)
				{
					StringBuilder sbConditions = new StringBuilder();
					foreach (ColumnRelationship columnRelationship in relation.ColumnRelationships)
					{
						Column childColumn = (Column)columnRelationship.ChildColumnRef.Object;
						if (childColumn.PrimaryKey)
						{
							string isNull = relation.IsRequired() ? "true" : "false";
							sbConditions.AppendFormat("      <Condition ColumnName=\"{0}\" IsNull=\"{1}\" />", childColumn.DatabaseName, isNull).AppendLine();
						}
					}

					sb.AppendFormat("    <AssociationSetMapping Name=\"{0}\" TypeName=\"{1}.{0}\" StoreEntitySet=\"{2}\">", relation.GetCodeFkName(), this.DefaultNamespace, childEntity.DatabaseName).AppendLine();
					sb.AppendFormat("      <EndProperty Name=\"{1}{0}\">", parentEntity.PascalName, relation.RoleName).AppendLine();
					foreach (ColumnRelationship columnRelationship in relation.ColumnRelationships)
					{
						Column childColumn = (Column)columnRelationship.ChildColumnRef.Object;
						Column parentColumn = (Column)columnRelationship.ParentColumnRef.Object;
						sb.AppendFormat("        <ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", parentColumn.PascalName, childColumn.DatabaseName).AppendLine();
					}

					sb.AppendLine("      </EndProperty>");
					sb.AppendFormat("      <EndProperty Name=\"{1}{0}List\">", childEntity.PascalName, relation.RoleName).AppendLine();
					foreach (Column column in childEntity.PrimaryKeyColumns.OrderBy(x => x.Name))
					{
						sb.AppendFormat("        <ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", column.PascalName, column.DatabaseName).AppendLine();
					}

					sb.AppendLine("      </EndProperty>");
					sb.Append(sbConditions);
					sb.AppendFormat("    </AssociationSetMapping>").AppendLine();
				}
			}
		}

		private void AppendTableEntityMappingSet()
		{
			foreach (Table currentTable in _model.Database.Tables.Where(x => x.Generated).OrderBy(x => x.Name))
			{
				sb.AppendFormat("		<EntitySetMapping Name=\"{0}\">", currentTable.PascalName).AppendLine();
				sb.AppendFormat("			<EntityTypeMapping TypeName=\"IsTypeOf({0}.{1})\">", this.DefaultNamespace, currentTable.PascalName).AppendLine();
				AppendMappingFragment(currentTable);
				sb.AppendFormat("			</EntityTypeMapping>").AppendLine();
				sb.AppendFormat("			<EntityTypeMapping TypeName=\"{0}.{1}\">", this.DefaultNamespace, currentTable.PascalName).AppendLine();
				sb.AppendFormat("				<ModificationFunctionMapping>").AppendLine();
				AppendInsertMap(currentTable);
				AppendUpdateMap(currentTable);
				AppendDeleteMap(currentTable);
				sb.AppendFormat("				</ModificationFunctionMapping>").AppendLine();
				sb.AppendFormat("			</EntityTypeMapping>").AppendLine();
				sb.AppendFormat("		</EntitySetMapping>").AppendLine();
			}
		}

		private void AppendMappingFragment(Table currentTable)
		{
			sb.AppendFormat("				<MappingFragment StoreEntitySet=\"{0}\">", currentTable.DatabaseName).AppendLine();
			foreach (Column currentColumn in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
			{
				if (currentColumn.PrimaryKey || !currentColumn.IsForeignKey())
					sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", currentColumn.PascalName, currentColumn.DatabaseName).AppendLine();
			}

			if (currentTable.AllowCreateAudit)
			{
				sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedByColumnName), _model.Database.CreatedByColumnName).AppendLine();
				sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedDateColumnName), _model.Database.CreatedDateColumnName).AppendLine();
			}

			if (currentTable.AllowModifiedAudit)
			{
				sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName), _model.Database.ModifiedByColumnName).AppendLine();
				sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedDateColumnName), _model.Database.ModifiedDateColumnName).AppendLine();
			}

			if (currentTable.AllowTimestamp)
			{
				sb.AppendFormat("					<ScalarProperty Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.TimestampColumnName), _model.Database.TimestampColumnName).AppendLine();
			}

			sb.AppendFormat("				</MappingFragment>").AppendLine();
		}

		private void AppendInsertMap(Table currentTable)
		{
			sb.AppendFormat("					<InsertFunction FunctionName=\"{0}.Store.gen_{1}Insert\">", this.DefaultNamespace, currentTable.PascalName).AppendLine();
			AppendInsertMapScalar(currentTable);
			AppendInsertReturnProperties(currentTable);
			sb.AppendFormat("					</InsertFunction>").AppendLine();
		}

		private void AppendInsertReturnProperties(Table currentTable)
		{
			foreach (Column currentColumn in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
			{
				if (!(currentColumn.PrimaryKey && currentColumn.Identity != IdentityTypeConstants.Database) && !currentColumn.IsForeignKey())
					sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", currentColumn.PascalName, currentColumn.DatabaseName).AppendLine();
			}

			if (currentTable.AllowCreateAudit)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedByColumnName), _model.Database.CreatedByColumnName).AppendLine();
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedDateColumnName), _model.Database.CreatedDateColumnName).AppendLine();
			}

			if (currentTable.AllowModifiedAudit)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName), _model.Database.ModifiedByColumnName).AppendLine();
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedDateColumnName), _model.Database.ModifiedDateColumnName).AppendLine();
			}

			if (currentTable.AllowTimestamp)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.TimestampColumnName), _model.Database.TimestampColumnName).AppendLine();
			}
		}

		private void AppendInsertMapScalar(Table currentTable)
		{
			Dictionary<string, Column> fkColumns = new Dictionary<string, Column>();
			foreach (Relation currentRelation in currentTable.ChildRoleRelations)
			{
				if (currentRelation.FkColumns.Count(x => x.PrimaryKey) == 0)
				{
					Table parentTable = (Table)currentRelation.ParentTableRef.Object;
					Table childTable = (Table)currentRelation.ChildTableRef.Object;
					if (childTable.Generated && parentTable.Generated)
					{
						sb.AppendFormat("						<AssociationEnd AssociationSet=\"{0}\" From=\"{3}{1}List\" To=\"{3}{2}\">", currentRelation.GetCodeFkName(), childTable.PascalName, parentTable.PascalName, currentRelation.RoleName).AppendLine();
						foreach (ColumnRelationship columnRelationship in currentRelation.ColumnRelationships)
						{
							Column parentColumn = (Column)columnRelationship.ParentColumnRef.Object;
							Column childColumn = (Column)columnRelationship.ChildColumnRef.Object;
							sb.AppendFormat("							<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", parentColumn.PascalName, childColumn.DatabaseName).AppendLine();
							fkColumns.Add(childColumn.Key, childColumn);
						}
						sb.AppendLine("						</AssociationEnd>");
					}
				}
			}

			foreach (Column currentColumn in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
			{				
				if (currentColumn.RelationshipRef == null &&
					currentColumn.Identity != IdentityTypeConstants.Database &&
					!currentColumn.ComputedColumn && !fkColumns.ContainsKey(currentColumn.Key))
				{
					sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", currentColumn.PascalName, currentColumn.DatabaseName).AppendLine();
				}
			}

			if (currentTable.AllowCreateAudit)
			{

				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedByColumnName), _model.Database.CreatedByColumnName).AppendLine();
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedDateColumnName), _model.Database.CreatedDateColumnName).AppendLine();
			}

			if (currentTable.AllowModifiedAudit)
			{

				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName), _model.Database.ModifiedByColumnName).AppendLine();
			}

		}

		private void AppendUpdateMap(Table currentTable)
		{
			string spName = string.Format("{0}.Store.gen_{1}Update", this.DefaultNamespace, currentTable.PascalName);
			if (currentTable.AssociativeTable) spName = string.Format("{0}.Store.gen_NOOP", this.DefaultNamespace, currentTable.PascalName);
			sb.AppendLine("					<UpdateFunction FunctionName=\"" + spName + "\">");
			if (!currentTable.AssociativeTable)
			{
				AppendUpdateMapScalarProperties(currentTable);
				AppendUpdateMapReturnProperties(currentTable);
			}
			sb.AppendFormat("					</UpdateFunction>").AppendLine();
		}

		private void AppendUpdateMapReturnProperties(Table currentTable)
		{
			foreach (Column currentColumn in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
			{
				if (!currentColumn.PrimaryKey && !currentColumn.IsForeignKey())
				{
					sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", currentColumn.PascalName, currentColumn.DatabaseName);
					sb.AppendLine();
				}
			}

			if (currentTable.AllowCreateAudit)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedByColumnName), _model.Database.CreatedByColumnName);
				sb.AppendLine();
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.CreatedDateColumnName), _model.Database.CreatedDateColumnName);
				sb.AppendLine();
			}

			if (currentTable.AllowModifiedAudit)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName), _model.Database.ModifiedByColumnName);
				sb.AppendLine();
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedDateColumnName), _model.Database.ModifiedDateColumnName);
				sb.AppendLine();
			}

			if (currentTable.AllowTimestamp)
			{
				sb.AppendFormat("						<ResultBinding Name=\"{0}\" ColumnName=\"{1}\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.TimestampColumnName), _model.Database.TimestampColumnName);
				sb.AppendLine();
			}

		}

		private void AppendUpdateMapScalarProperties(Table currentTable)
		{
			if (currentTable.AllowTimestamp)
			{
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"Original_{1}\" Version=\"Original\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.TimestampColumnName), _model.Database.TimestampColumnName);
				sb.AppendLine();
			}

			Dictionary<string, Column> fkColumns = new Dictionary<string, Column>();
			foreach (Relation currentRelation in currentTable.ChildRoleRelations)
			{
				if (currentRelation.FkColumns.Count(x => x.PrimaryKey) == 0)
				{
					Table parentTable = (Table)currentRelation.ParentTableRef.Object;
					Table childTable = (Table)currentRelation.ChildTableRef.Object;
					if (childTable.Generated && parentTable.Generated)
					{						
						sb.AppendFormat("						<AssociationEnd AssociationSet=\"{0}\" From=\"{3}{1}List\" To=\"{3}{2}\">", currentRelation.GetCodeFkName(), childTable.PascalName, parentTable.PascalName, currentRelation.RoleName).AppendLine();
						foreach (ColumnRelationship columnRelationship in currentRelation.ColumnRelationships)
						{
							Column parentColumn = (Column)columnRelationship.ParentColumnRef.Object;
							Column childColumn = (Column)columnRelationship.ChildColumnRef.Object;							
							sb.AppendFormat("							<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" Version=\"Current\" />", parentColumn.PascalName, childColumn.DatabaseName);
							sb.AppendLine();
							fkColumns.Add(childColumn.Key, childColumn);
						}
						sb.AppendLine("						</AssociationEnd>");
					}
				}
			}

			foreach (Column currentColumn in currentTable.PrimaryKeyColumns.OrderBy(x => x.Name))
			{
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"Original_{1}\" Version=\"Original\" />", currentColumn.PascalName, currentColumn.DatabaseName);
				sb.AppendLine();
			}

			foreach (Column currentColumn in currentTable.GetColumns().Where(x => x.Generated).OrderBy(x => x.Name))
			{
				if (currentColumn.Generated && currentColumn.RelationshipRef == null && !currentColumn.PrimaryKey && !currentColumn.ComputedColumn && !fkColumns.ContainsKey(currentColumn.Key))
				{
					sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" Version=\"Current\" />", currentColumn.PascalName, currentColumn.DatabaseName);
					sb.AppendLine();
				}
			}

			if (currentTable.AllowModifiedAudit)
			{
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" Version=\"Current\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedByColumnName), _model.Database.ModifiedByColumnName);
				sb.AppendLine();
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" Version=\"Current\" />", StringHelper.DatabaseNameToPascalCase(_model.Database.ModifiedDateColumnName), _model.Database.ModifiedDateColumnName);
				sb.AppendLine();
			}
		}

		private void AppendDeleteMap(Table currentTable)
		{
			sb.AppendFormat("					<DeleteFunction FunctionName=\"{0}.Store.gen_{1}Delete\" >", this.DefaultNamespace, currentTable.PascalName).AppendLine();
			foreach (Column currentColumn in currentTable.PrimaryKeyColumns.OrderBy(x => x.Name))
			{
				sb.AppendFormat("						<ScalarProperty Name=\"{0}\" ParameterName=\"Original_{1}\" />", currentColumn.PascalName, currentColumn.DatabaseName).AppendLine();
			}
			foreach (Relation currentRelation in currentTable.ChildRoleRelations)
			{
				Table parentTable = (Table)currentRelation.ParentTableRef.Object;
				Table childTable = (Table)currentRelation.ChildTableRef.Object;
				if (childTable.Generated && parentTable.Generated)
				{
					//if (currentRelation.FkColumns.Count(x => x.PrimaryKey) == 0)
					//{
						sb.AppendFormat("						<AssociationEnd AssociationSet=\"{0}\" From=\"{3}{1}List\" To=\"{3}{2}\">", currentRelation.GetCodeFkName(), childTable.PascalName, parentTable.PascalName, currentRelation.RoleName);
						sb.AppendLine();
						foreach (ColumnRelationship columnRelationship in currentRelation.ColumnRelationships)
						{
							Column parentColumn = (Column)columnRelationship.ParentColumnRef.Object;
							Column childColumn = (Column)columnRelationship.ChildColumnRef.Object;
							sb.AppendFormat("							<ScalarProperty Name=\"{0}\" ParameterName=\"{1}\" />", parentColumn.PascalName, currentRelation.RoleName + parentTable.DatabaseName + "_" + childColumn.DatabaseName).AppendLine();
						}
						sb.AppendLine("						</AssociationEnd>");
					//}
				}
			} sb.AppendFormat("					</DeleteFunction>").AppendLine();
		}

		#endregion


	}
}